using System;
using System.ComponentModel;
using System.Runtime.Serialization;

namespace Ns.Data.Entity
{
    /// <summary>
    /// Represents an entity supporting change tracking via change notification.
    /// </summary>
#if !SILVERLIGHT
    [Serializable]
#endif
    [DataContract]
    public abstract class PersistEntity 
        : NotifyEntity
        , IPersistEntity
    {
        /// <ignore/>
        protected const string PersistChangePropertyName = "PersistChange";

        /// <summary>
        /// Gets the value of the current <see cref="ChangeTracking"/>.
        /// </summary>
        /// <value>The value of the current change tracking.</value>
        /// <remarks>The default is value <see cref="F:ChangeTracking.Enabled"/> 
        /// but it can be overriden via the <see cref="ChangeTrackingScope"/>.</remarks>
        internal static ChangeTracking CurrentChangeTracking
        {
            get
            {
                return ChangeTrackingScope.Current ?? ChangeTracking.Enabled;
            }
        }

        /// <summary>
        /// Gets the value of the current <see cref="PersistChange"/>.
        /// </summary>
        /// <value>The value of the current persist change.</value>
        /// <remarks>This value is ignored by default
        /// but it can be overriden via the <see cref="PersistChangeScope"/>.</remarks>
        internal static PersistChange? CurrentPersistChange
        {
            get
            {
                return PersistChangeScope.Current ?? default(PersistChange?);
            }
        }

        #region IPersistChange Members

        /// <ignore/>
        protected PersistChange _persistChange;

        /// <summary>
        /// Gets or sets the persist change of the current <see cref="PersistEntity"/>.
        /// </summary>
        /// <value>The persist change.</value>
        [DataMember(EmitDefaultValue = false)]
        public virtual PersistChange PersistChange
        {
            get { return _persistChange; }
            set
            {
                if (_persistChange != value)
                {
                    _persistChange = value;
                    OnPropertyChanged(PersistChangePropertyName);
                }
            }
        }

        #region INotifyPropertyChanged Members

        /// <summary>
        /// Raises the <see cref="E:PersistEntity.PropertyChanged"/> event.
        /// </summary>
        /// <param name="e">A <see cref="T:System.ComponentModel.PropertyChangedEventArgs"/> that contains the event data.</param>
        protected override void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            if (IgnoreOnPropertyChanged)
                return;

            if (CurrentChangeTracking == ChangeTracking.Enabled &&
                CurrentPersistChange == null &&
                !string.Equals(e.PropertyName, PersistChangePropertyName))
            {
                Touch();
            }

            base.OnPropertyChanged(e);
        }

        /// <summary>
        /// Touches this instance if no changes were made before.
        /// </summary>
        public virtual void Touch()
        {
            if (PersistChange == PersistChange.None)
                PersistChange = PersistChange.Update;
        }

        #endregion

        #endregion

        /// <summary>
        /// Converts the value of the current <see cref="PersistEntity"/> object to its equivalent string representation. 
        /// </summary>
        /// <returns>A string representation of the value of the current <see cref="PersistEntity"/> object.</returns>        
        public override string ToString()
        {
            return string.Format("PersistChange = {0}, IgnoreOnPropertyChanged = {1}"
                , PersistChange
                , IgnoreOnPropertyChanged);
        }

        #region Deserialization Members

#if !SILVERLIGHT
    [NonSerialized]
#endif
        private bool _ignoreOnPropertyChanged;

        /// <summary>
        /// Gets a value indicating whether the <see cref="NotifyEntity.PropertyChanged"/> event is currently ignored or not.
        /// </summary>
        /// <value>
        /// 	<b>true</b> if the <see cref="NotifyEntity.PropertyChanged"/> event is currently ignored; otherwise, <b>false</b>.
        /// </value>
        protected virtual bool IgnoreOnPropertyChanged 
        {
            get { return _ignoreOnPropertyChanged; }
        }

        /// <ignore/>
        /// <remarks>This methods must be public; otherwise Silverlight serializers will not be able to use it.</remarks>
        [OnDeserializing]
        public void OnDeserializing(StreamingContext context) { _ignoreOnPropertyChanged = true; }

        /// <ignore/>        
        /// <remarks>This methods must be public; otherwise Silverlight serializers will not be able to use it.</remarks>
        [OnDeserialized]
        public void OnDeserialized(StreamingContext context) { _ignoreOnPropertyChanged = false; }

        #endregion

        private static ChangeTracking GetDefaultChangeTracking()
        {
            return ChangeTracking.Enabled;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="PersistEntity"/> class 
        /// with the <see cref="CurrentPersistChange"/>
        /// or according to the <see cref="CurrentChangeTracking"/>.
        /// </summary>
        public PersistEntity()
            : this(CurrentPersistChange 
            ?? ((CurrentChangeTracking == ChangeTracking.Enabled)
                ? PersistChange.Insert
                : PersistChange.None))
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="PersistEntity"/> class 
        /// with the specified <see cref="PersistChange"/>.
        /// </summary>
        /// <param name="persistChange">The persist change.</param>
        public PersistEntity(PersistChange persistChange)
        {
            _persistChange = persistChange;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="PersistEntity"/> class
        /// copied from the specified <paramref name="entity"/>.
        /// </summary>
        /// <param name="entity">The entity.</param>
        /// <remarks>This constructor ignores any <see cref="ChangeTrackingScope"/> and <see cref="PersistChangeScope"/>.</remarks>
        /// <exception cref="ArgumentNullException"><paramref name="entity"/> is null.</exception>
        protected PersistEntity(PersistEntity entity)
        {
            if (entity == null)
                throw new ArgumentNullException("entity");

            this._persistChange = entity._persistChange;
        }
    }
}